package com.xueyi.job.util;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.xueyi.common.cache.utils.DictUtil;
import com.xueyi.common.core.constant.basic.HttpConstants;
import com.xueyi.common.core.constant.basic.SecurityConstants;
import com.xueyi.common.core.context.SecurityContextHolder;
import com.xueyi.common.core.exception.ServiceException;
import com.xueyi.common.core.exception.UtilException;
import com.xueyi.common.core.utils.core.CollUtil;
import com.xueyi.common.core.utils.core.NumberUtil;
import com.xueyi.common.core.utils.core.ObjectUtil;
import com.xueyi.common.core.utils.core.SpringUtil;
import com.xueyi.common.core.utils.core.StrUtil;
import com.xueyi.common.core.utils.http.HttpRequest;
import com.xueyi.common.core.web.result.R;
import com.xueyi.job.api.domain.dto.SysJobDto;
import com.xueyi.job.constant.ScheduleConstants;
import com.xueyi.system.api.dict.domain.po.SysDictDataPo;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 任务执行工具类
 *
 * @author xueyi
 */
@Slf4j
public class JobInvokeUtil {

    /**
     * 执行方法
     *
     * @param sysJob 系统任务
     */
    public static void invokeMethod(SysJobDto sysJob) throws Exception {
        ScheduleConstants.JobGroupType jobGroupType = ScheduleConstants.JobGroupType.getByCode(sysJob.getJobGroup());
        String enterpriseId = sysJob.getInvokeTenant();
        switch (jobGroupType) {
            case DEFAULT -> {
                String invokeTarget = sysJob.getInvokeTarget();
                String beanName = getBeanName(invokeTarget);
                String methodName = getMethodName(invokeTarget);
                List<Object[]> methodParams = getMethodParams(invokeTarget);
                if (!isValidClassName(beanName)) {
                    Object bean = SpringUtil.getBean(beanName);
                    invokeMethod(bean, methodName, methodParams, enterpriseId);
                } else {
                    Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
                    invokeMethod(bean, methodName, methodParams, enterpriseId);
                }
            }
            case INNER_SYSTEM, EXTERNAL_SYSTEM -> {
                Map<String, String> headersMap = new HashMap<>();
                String aprUrl;
                // 内部接口调用
                if (ObjectUtil.equals(jobGroupType, ScheduleConstants.JobGroupType.INNER_SYSTEM)) {
                    try {
                        // 创建 NamingService 实例
                        NacosDiscoveryProperties nacosDiscoveryProperties = SpringUtil.getBean(NacosDiscoveryProperties.class);
                        NamingService namingService = NacosFactory.createNamingService(nacosDiscoveryProperties.getNacosProperties());

                        // 获取指定服务的实例
                        String serviceRoute = Optional.ofNullable(DictUtil.getDictCache(ScheduleConstants.DictType.SYS_JOB_INNER_TYPE.getCode()))
                                .flatMap(list -> list.stream().filter(item -> StrUtil.equals(item.getValue(), sysJob.getServerType())).findFirst())
                                .map(SysDictDataPo::getAdditionalA)
                                .orElseThrow(() -> new ServiceException("执行失败，服务{}不存在！", sysJob.getServerType()));

                        List<Instance> instances = namingService.getAllInstances(serviceRoute);

                        if (CollUtil.isNotEmpty(instances)) {
                            Instance instance = instances.get(0);
                            aprUrl = StrUtil.format("http://{}:{}{}", instance.getIp(), instance.getPort(),
                                    StrUtil.startWith(sysJob.getApiUrl(), StrUtil.SLASH)
                                            ? sysJob.getApiUrl()
                                            : StrUtil.SLASH + sysJob.getApiUrl());
                            // 内部请求标识
                            headersMap.put(SecurityConstants.FROM_SOURCE, SecurityConstants.INNER);
                        } else {
                            throw new UtilException("【定时任务】没有找到服务实例，任务名：{}", sysJob.getName());
                        }
                    } catch (NacosException e) {
                        throw new UtilException("【定时任务】获取Nacos服务实例失败，任务名：{}，异常原因：", sysJob.getName(), e);
                    }
                } else {
                    aprUrl = sysJob.getApiUrl();
                }
                HttpConstants.HttpType httpType = HttpConstants.HttpType.getByCode(sysJob.getHttpType());
                try {
                    String result = switch (httpType) {
                        case GET -> HttpRequest.get(aprUrl)
                                .headerMap(headersMap, Boolean.TRUE)
                                .execute().body();
                        case POST -> HttpRequest.post(aprUrl)
                                .headerMap(headersMap, Boolean.TRUE)
                                .execute().body();
                        case PUT -> HttpRequest.put(aprUrl)
                                .headerMap(headersMap, Boolean.TRUE)
                                .execute().body();
                        case DELETE -> HttpRequest.delete(aprUrl)
                                .headerMap(headersMap, Boolean.TRUE)
                                .execute().body();
                    };
                    sysJob.setResult(result);
                    Optional.ofNullable(result)
                            .map(item -> JSONObject.parseObject(item, R.class))
                            .ifPresent(info -> {
                                if (info.isOk()) {
                                    log.info("【定时任务】请求接口成功，任务名：{}，接口地址：{}，返回结果：{}", sysJob.getName(), aprUrl, result);
                                } else {
                                    log.error("【定时任务】请求接口失败，任务名：{}，接口地址：{}，返回结果：{}", sysJob.getName(), aprUrl, result);
                                    throw new ServiceException("任务执行失败，失败原因:{}", info.getMsg());
                                }
                            });
                } catch (Exception e) {
                    throw new UtilException("【定时任务】请求接口失败，任务名：{}，接口地址：{}，异常原因：", sysJob.getName(), aprUrl, e);
                }
            }
        }
    }

    /**
     * 调用任务方法
     *
     * @param bean         目标对象
     * @param methodName   方法名称
     * @param methodParams 方法参数
     * @param enterpriseId 租户Id
     */
    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams, String enterpriseId)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        if (StrUtil.isBlank(enterpriseId)) {
            SecurityContextHolder.setEnterpriseId(enterpriseId);
        }
        if (CollUtil.isNotEmpty(methodParams)) {
            Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
            method.invoke(bean, getMethodParamsValue(methodParams));
        } else {
            Method method = bean.getClass().getMethod(methodName);
            method.invoke(bean);
        }
    }

    /**
     * 校验是否为为class包名
     *
     * @param invokeTarget 名称
     * @return true是 false否
     */
    public static boolean isValidClassName(String invokeTarget) {
        return StrUtil.count(invokeTarget, ".") > NumberUtil.One;
    }

    /**
     * 获取bean名称
     *
     * @param invokeTarget 目标字符串
     * @return bean名称
     */
    public static String getBeanName(String invokeTarget) {
        String beanName = StrUtil.subBefore(invokeTarget, StrUtil.PARENTHESES_START);
        return StrUtil.subBeforeLast(beanName, StrUtil.DOT);
    }

    /**
     * 获取bean方法
     *
     * @param invokeTarget 目标字符串
     * @return method方法
     */
    public static String getMethodName(String invokeTarget) {
        String methodName = StrUtil.subBefore(invokeTarget, StrUtil.PARENTHESES_START);
        return StrUtil.subAfterLast(methodName, StrUtil.DOT);
    }

    /**
     * 获取method方法参数相关列表
     *
     * @param invokeTarget 目标字符串
     * @return method方法相关参数列表
     */
    public static List<Object[]> getMethodParams(String invokeTarget) {
        String methodStr = StrUtil.subBetween(invokeTarget, StrUtil.PARENTHESES_START, StrUtil.PARENTHESES_END);
        if (StrUtil.isEmpty(methodStr))
            return null;
        String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
        List<Object[]> clazz = new LinkedList<>();
        for (String methodParam : methodParams) {
            String str = StrUtil.trimToEmpty(methodParam);
            // String字符串类型，以'或"开头
            if (StrUtil.startWithAny(str, "'", "\"")) {
                clazz.add(new Object[]{StrUtil.sub(str, NumberUtil.One, str.length() - NumberUtil.One), String.class});
            }
            // boolean布尔类型，等于true或者false
            else if (StrUtil.TRUE.equalsIgnoreCase(str) || StrUtil.FALSE.equalsIgnoreCase(str)) {
                clazz.add(new Object[]{Boolean.valueOf(str), Boolean.class});
            }
            // long长整形，以L结尾
            else if (StrUtil.endWith(str, "L")) {
                clazz.add(new Object[]{Long.valueOf(StrUtil.sub(str, NumberUtil.Zero, str.length() - NumberUtil.One)), Long.class});
            }
            // double浮点类型，以D结尾
            else if (StrUtil.endWith(str, "D")) {
                clazz.add(new Object[]{Double.valueOf(StrUtil.sub(str, NumberUtil.Zero, str.length() - NumberUtil.One)), Double.class});
            }
            // 其他类型归类为整形
            else {
                clazz.add(new Object[]{Integer.valueOf(str), Integer.class});
            }
        }
        return clazz;
    }

    /**
     * 获取参数类型
     *
     * @param methodParams 参数相关列表
     * @return 参数类型列表
     */
    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
        Class<?>[] clazz = new Class<?>[methodParams.size()];
        int index = NumberUtil.Zero;
        for (Object[] os : methodParams) {
            clazz[index] = (Class<?>) os[NumberUtil.One];
            index++;
        }
        return clazz;
    }

    /**
     * 获取参数值
     *
     * @param methodParams 参数相关列表
     * @return 参数值列表
     */
    public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
        Object[] clazz = new Object[methodParams.size()];
        int index = NumberUtil.Zero;
        for (Object[] os : methodParams) {
            clazz[index] = os[NumberUtil.Zero];
            index++;
        }
        return clazz;
    }
}
